#ifndef _BRUSH_H
#define _BRUSH_H


//class for reading PPM file

class ViewMatrix 
{
public:
	typedef float value_type ;
	typedef math::vector<value_type, 2> vector2 ;
	typedef math::vector<value_type, 3> vector3 ;
	typedef math::vector<value_type, 2> point2 ;
	typedef math::vector<value_type, 3> point3 ;
	ViewMatrix () {}
	ViewMatrix (ViewMatrix & vm)
	{
		for (int i = 0; i < 16; ++i )
		{
			this->modelViewMatrix[i] = vm.modelViewMatrix[i];
			this->projectionMatrix[i] = vm.projectionMatrix[i];
                        
		}
		for (int i = 0; i < 4; ++i )
		{
			this->viewPort [i] = vm.viewPort[i];
		}
	}
	void getCoordEnv()
	{
		glGetDoublev (GL_MODELVIEW_MATRIX, this->modelViewMatrix);
		glGetDoublev (GL_PROJECTION_MATRIX, this->projectionMatrix);
		glGetIntegerv(GL_VIEWPORT, this->viewPort);     
	}
	double modelViewMatrix[16];
	double projectionMatrix[16];  
	int viewPort[4];

	inline vector3 mappingObj2Win (const point2 & objCoord)
	{
		point3 objc ( objCoord.x, objCoord.y , 0); 
		return mappingObj2Win(objc); 
	}
	inline vector3 mappingObj2Win (const point3 & objCoord)
	{
		double winx, winy, winz ;
		gluProject(objCoord.x, objCoord.y, objCoord.z, modelViewMatrix, projectionMatrix, viewPort,
				   & winx, & winy, & winz );
		winy = m_winheight - winy ;
		return vector3 (float(winx), float(winy), float(winz));
	}

	inline vector3 mappingWin2Obj (const vector3 & winPos)
	{
		double x, y, z;
		//gluUnProject(winPos.x, m_winheight - winPos.y, winPos.z, modelViewMatrix, projectionMatrix, viewPort, & x, &y , &z);
		gluUnProject(winPos.x, m_winheight - winPos.y, winPos.z, modelViewMatrix, projectionMatrix, viewPort, & x, &y , &z);
		return vector3 (float(x), float(y), float(z));
	}

	void pick_ray(const point2 & winPos, point3 & p1, point3 & p2)
	{
		// p1 is on z-near
		// p2 is on z-far
		p1  = mappingWin2Obj(vector3 (winPos.x, winPos.y, 0.0f));
		p2  = mappingWin2Obj(vector3 (winPos.x, winPos.y, 1.0f));
	}

	void set_winsize (unsigned width, unsigned height) 
	{
		this->m_winwidth = width ; this->m_winheight = height ;
	}
private :
	unsigned m_winheight ;
	unsigned m_winwidth ;
};


class PPM
{
public:
	typedef math::vector<short, 3> irgb_type ;
	PPM() {}
	friend std::istream & operator >> (std::istream & istr, PPM & ppm)
	{
		std::string str ;
		getline(istr, str); // read the first line
		getline(istr, str); // read the second line
		istr>> ppm.width >> ppm.height ; // read the third line
		getline(istr, str) ; // read the third line end
		getline(istr, str) ; // read the forth line

		ppm.data = new irgb_type * [ppm.height];
		for (unsigned i = 0; i < ppm.height; ++i)
		{
			ppm.data[i] = new irgb_type [ppm.width];
			for (unsigned j = 0; j< ppm.width ; ++j)
			{
                                
				istr>>(ppm.data[i][j]).r >> ppm.data[i][j].g >> ppm.data[i][j].b; 

			}
		}
		return istr ;
	}
	virtual ~PPM() {this->finalize();}
	inline const int getWidth() const {return this->width ;}
	inline const int getHeight() const {return this->height ;}
	inline const irgb_type & getColor (unsigned x, unsigned y) const
	{
		return data[x][y];
	}
private:

	void finalize ()
	{
		for (unsigned i = 0;i < height; ++i)
		{
			delete []data[i];
		}
		delete []data;
	}
private:
	unsigned width, height ;
	irgb_type * * data ;
};


class Brush
{
public:
	typedef float value_type ;
	typedef math::vector<float, 4> frgba_type ;

	typedef math::vector<value_type, 2> vector2 ;
	typedef math::vector<value_type, 3> vector3 ;
	typedef math::vector<value_type, 2> point2 ;
	typedef math::vector<value_type, 3> point3 ;
	typedef math::hierarchy_mesh_traits <value_type, 3>::FaceType face_type ;
	typedef math::hierarchy_mesh_traits <value_type, 3>::VertexType vertex_type ;
	typedef math::hierarchy_mesh_traits <value_type, 3>::HalfEdgeType halfedge_type ;

	typedef math::HierarchyMesh<math::hierarchy_mesh_traits<value_type, 3> >  mesh_type ;
	typedef math::PseudoMesh<value_type , 3> pseudomesh_type ;

	//Brush(): brush_size(0.0005), color() {}
Brush(): size(0.0025), color(0,0,0, 0),tex_coord(vector2 (this->m_winwidth /2 , this->m_winheight /2)), win_pos(vector2 (this->m_winwidth/2, this->m_winheight /2)), pixel_size(50) , m_layer_id(1), m_is_blend(false)
	{ }
	Brush(const Brush & brush)
	{
		this->brushVM = brush.brushVM ;
		this->texVM = brush.texVM ;
		this->objVM = brush.objVM ;
		this->tex_coord = brush.tex_coord ;
		this->win_pos = brush.win_pos ;
		this->size = brush.size ;
		this->color = brush.color ;
		this->pixel_size = brush.pixel_size ;
	}
	virtual ~ Brush() {}
	virtual inline const float get_size() const {return size ;}
	virtual inline void set_winsize (unsigned width, unsigned height) 
	{ 
		this->m_winwidth = width ; this->m_winheight = height ;
		this->brushVM.set_winsize (width, height);
		this->objVM.set_winsize (width, height);
	}
 	virtual inline void set_size(float size) {this->size = size ;}
	virtual inline const float get_pixel_size() const {return pixel_size ;}
	virtual inline void set_pixel_size(float size) {this->pixel_size = size ;}
	virtual inline void set_win_pos (const vector2 & pos ) {this->win_pos = pos;}
	virtual inline const vector2 & get_win_pos () const {return this->win_pos; }
	virtual inline void set_tex_pos (const vector2 & pos ) {this->tex_coord = pos;}
	virtual inline const vector2 & get_tex_pos () const {return this->tex_coord; }

	virtual inline void set_color (frgba_type & c ) {this->color = c;}
	virtual inline const frgba_type & get_color () const { return this->color ;}
	virtual void set_transparent_color (const frgba_type & color ) {}
	virtual bool load_pattern (const char * patternFileName) { return true; }
	virtual void draw ()
	{
		draw(win_pos);
	}
	virtual void tex_draw()
	{
		draw(tex_coord);
	}
	virtual void draw (vector2 mousePos) 
	{
		vector3 objPos = brushVM.mappingWin2Obj(vector3(mousePos.x, mousePos.y, .99f));
		size_pixel2obj();
		glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
		glEnable(GL_BLEND) ;
		glLineWidth(3) ;
		glBegin(GL_LINE_LOOP) ;
		glColor4f(0.75,0.75,0.75,0.75);
		for (float angle = 0.0f ; angle <= 3.1415926f * 2.0f + 0.1f ; angle += 3.1415926f * 2.0f/ 50.0f)
		{
			glVertex3f(objPos.x + cos(angle) * size,  objPos.y + sin(angle) * size, 0.0f);
		}
		glEnd();

		glLineWidth(1) ;
		glDisable(GL_BLEND);
	}

	void getObjCoordEnv ()          {       this->objVM.getCoordEnv();      }
	void getBrushCoordEnv()         {       this->brushVM.getCoordEnv();    }

	//This is check in texcoord;
/*
	bool isInBrush(const vector3 & objCoord)
	{
		float ratio ;
		return isInBrush(objCoord, ratio);
	}
*/
	// in meshview. 
	bool is_in_brush(const vector3 & objCoord)
	{
		vector3 winCoord = objVM.mappingObj2Win(objCoord) ;
		float distance = ((winCoord.x - win_pos.x) * (winCoord.x - win_pos.x)
							  + (winCoord.y - win_pos.y) * (winCoord.y - win_pos.y));
		
		if (  distance <= pixel_size * pixel_size ) return true; 
		return false ;
	}

	bool is_intersect_brush (face_type * face)
	{
		std::vector<point3 * > v_coord ;
		face->Face2VertexCoord(v_coord); 
		std::vector<point3> v_win_coord (v_coord.size()) ;
		for (unsigned i = 0; i < v_coord.size(); ++i)
		{
			v_win_coord[i] = objVM.mappingObj2Win(*v_coord[i]); 
		}
		float aug_pixel_size = pixel_size * 2.0;
		// test point ;
		for (unsigned i = 0; i < v_win_coord.size(); ++i)
		{
			point3 &  winCoord = v_win_coord[i] ;
			value_type distance = ((winCoord.x - win_pos.x) * (winCoord.x - win_pos.x)
							  + (winCoord.y - win_pos.y) * (winCoord.y - win_pos.y));

			if (  distance <= aug_pixel_size * aug_pixel_size ) return true; 					
		}
		
		// test segment ;
		unsigned size = v_win_coord.size();
		for (unsigned i = 0; i < size ; ++i)
		{
			point3 & p1 = v_win_coord[i] ;
			p1.z = 0; 
			point3 & p2 = v_win_coord[(i + 1)% size] ;
			p2.z = 0; 
			vector3 v1 (p1.x - win_pos.x, p1.y - win_pos.y, p1.z) ;
//			std::cout<<"v1 "<<v1<<std::endl;
			vector3 v2 = p1 - p2 ; v2.z = 0; 
			v2.normalize(); 
//			std::cout<<"v2 "<<v2<<std::endl;
			vector3 v3 = v1 - v2 * (v1 * v2) ;
//			std::cout<<"v3 "<<v3<<std::endl;
			if (v3 * v3 < aug_pixel_size * aug_pixel_size )
			{
				v3.x += win_pos.x ; v3.y += win_pos.y; 
				if ((p1 - v3) * (v3 - p2) >= 0 ) return true; 
			}
		}
		
		return false; 
	}
	// in mapping view. 
	bool is_in_brush_tex(const vector3 & objCoord, value_type & ratio )
	{
		vector3 winCoord = texVM.mappingObj2Win(objCoord) ;

		point2 centerpos ;
		centerpos = ( m_winwidth ) / 2 * 3 , - int(m_winheight) / 2;

		float distance = ((winCoord.x - centerpos.x) * (winCoord.x - centerpos.x)
							  + (winCoord.y -  centerpos.y) * (winCoord.y -  centerpos.y));

//		pixel_size = 100 ;
		if (  distance <= pixel_size * pixel_size ) 
		{
			ratio = sqrt (distance ) / value_type (pixel_size) ; 
			return true; 
		}
		return false ;
	}

	inline bool isVisible(const std::vector<point3 *> & v_coord)
	{
		point3 p[3];
		for (int i = 0;i < 3; ++i)
		{
			p[i] = objVM.mappingObj2Win(*v_coord[i]); 
		}
		vector3 v1 = p[1]-p[0];
		vector3 v2 = p[2]-p[0];
		return  (v1^v2).z < 0;
	}
	inline vector3 mappingObj2Win (vector3 & objCoord)
	{
		return objVM.mappingObj2Win(objCoord);
	}

	void pick_ray(point3 & p1, point3 & p2)
	{
		objVM.pick_ray(win_pos, p1, p2);
	}       

	void get_brush_rect (int & x, int & y, unsigned & w, unsigned & h)
	{
		x = win_pos.x - pixel_size ;
		y = win_pos.y - pixel_size ;
		w = 2 * pixel_size ;
		h = 2 * pixel_size ;
	}


    //FIXME here the performance is bad.
	void index2barycentric (unsigned res, unsigned idx, unsigned & x, unsigned & y)
	{
        float m = (2.0f * res + 3); 
        x = int (0.5 * (m - sqrt (m*m - 8.0f * idx) )) ;
        y = idx - (res + 1 + res + 2 - x ) * x / 2 ;
	}
	void get_brush_pixel (int x, int y, unsigned w, unsigned h )
	{

	}
	void restore_brush_pixel(int x, int y, unsigned w, unsigned h )
	{

	}

	void update_brush_pixel_size()
	{
		size_obj2pixel();
	}
	void update_brush_size() 
	{
		size_pixel2obj();
	}

	virtual frgba_type evaluate_color (value_type ratio)
	{
		return get_color ();
	}
	virtual void paint (pseudomesh_type * uvexpand)
	{
		//Step 1. build the barrier 
		this->build_barrier(uvexpand);

		unsigned count = 0;
		for (unsigned i = 0;i < uvexpand->faces.size(); ++i)
		{
			std::vector<point2 *> p;
			face_type * modelFace = uvexpand->faces[i] ;

			// Step 2. filter the uncessary face
			if (false == this->filter_face(modelFace)) continue; 

			modelFace->Face2TexCoord(p);
			for (unsigned j = 0; j < modelFace->get_num_samples() ; ++j)
			{
				//map j to barycentric coords
				unsigned x, y;
				unsigned res = modelFace->get_resolution() ;
				index2barycentric(res, j, x, y);

				point2 point =  (*p[0]) * (float (x) / res)   + (*p[1]) * (float(y) / res )
					+ (*p[2]) * (1.0f -float(x+y) /res ) ;

				point3 p3 ( point.x, point.y ,0.0);
				value_type ratio; 
				if (is_in_brush_tex(p3, ratio))
				{
					//modelFace->set_color(m_layer_id, j, get_color());
					if (!m_is_blend)
						modelFace->set_color(m_layer_id, j, evaluate_color(ratio));
					else 
						modelFace->set_blend_color(m_layer_id, j, evaluate_color(ratio));
					count ++ ;
				}
			}
		}
		// step 3. clear the barrier ;
		this->clear_barrier();
	}

protected:
	virtual void size_obj2pixel ()
	{
		unsigned x = m_winwidth / 2;
		unsigned y = m_winheight / 2;
		vector3 objPos = brushVM.mappingWin2Obj(vector3 (x, y, 0.99f));
		objPos.x += this->size ;
		vector3 winPos = brushVM.mappingObj2Win(objPos); 
		this->pixel_size = winPos.x - x; 
	}

	virtual void size_pixel2obj()
	{
		vector3 p1 = brushVM.mappingWin2Obj(vector3(win_pos.x, win_pos.y, 0.99f));
		vector3 p2 = brushVM.mappingWin2Obj(vector3(win_pos.x + pixel_size, win_pos.y, 0.99f));
		this->size = p2.x - p1.x ;
	}

protected:

	virtual bool filter_face(face_type* face)
	{
		for (unsigned i = 0; i < this->barrier_face.size(); ++i)
		{
			if (face == this->barrier_face[i]) return false;
		}
		return true; 
	}


	virtual bool filter_strong (face_type* face)
	{
		bool result = true;
		// The mouse position is at (0,0)
		halfedge_type * he = face->he ;
		std::vector<point2 *> texCoord ;
		face->Face2TexCoord(texCoord);
		for (int i = 0; i < 3; ++i)
		{
			if (( (*texCoord[i])^ (*texCoord[(i+1)%3])) < 0 && face->get_strong_edge(i))
			{
				this->barrier.push_back(he);
				result = false; 
			}
			he = he->next ;
		}
		return result ;
	}



	virtual void build_barrier (pseudomesh_type * uvexpand)
	{
		for (unsigned i = 0; i < uvexpand->faces.size(); ++i)
		{
			face_type* modelface= uvexpand->faces[i] ;
			filter_strong (modelface); 
		}

		for (unsigned i = 0; i < this->barrier.size(); ++i)
		{
                        
			halfedge_type * he = barrier[i];
			vertex_type * center_v = (vertex_type * )he->from_v ;

			vector2 tex1  = he->from_v->m_texcoord ;
			vector2 tex2  = he->next->from_v->m_texcoord ;
               

			for ( vertex_type::VVIter v_iter = center_v->vv_begin(); v_iter != center_v->vv_end(); ++v_iter ) 
			{
				point2 p = (*v_iter)->m_texcoord ;
				if ( ((p-tex1) ^ (p-tex2)) > 0) barrier_face.push_back
													((face_type *)v_iter.get_he()->flip->face);
			}
			halfedge_type * heflip = barrier[i]->flip;
			vertex_type * center_v2 = (vertex_type *) heflip->from_v ;

			vector2 tex12  = heflip->from_v->m_texcoord ;
			vector2 tex22  = heflip->next->from_v->m_texcoord ;
                        
			for (vertex_type::VVIter v_iter2 = center_v2->vv_begin(); v_iter2 != center_v2->vv_end(); ++ v_iter2)
			{
				point2 p = (*v_iter2)->m_texcoord ;
				if ( ((p-tex12) ^ (p-tex22)) < 0) barrier_face.push_back
													  ((face_type *)v_iter2.get_he()->face);
			} 
		}
	}

	virtual void clear_barrier ()
	{
		this->barrier.clear();
		this->barrier_face.clear();
	}
	std::vector<halfedge_type * > barrier ;
	std::vector<face_type* > barrier_face ; 
public:

	ViewMatrix objVM ;
	ViewMatrix brushVM;
	ViewMatrix texVM;

protected:
	float size ; //Geometry size
	float pixel_size ; // Brush pixel size on the screen
	// Left and right view have the same pixel_size; 
	vector2 win_pos;
	vector2 tex_coord; 

	bool m_is_blend ; // true: blend brush; False: solid brush ; 

public:
        
        
	frgba_type color ;  // base color for this brush ; 

	unsigned m_winheight ;
	unsigned m_winwidth ;

	unsigned m_layer_id ;

public:
};

class SolidBrush : public Brush 
{
public :
	typedef float value_type ;
	typedef math::vector<float, 4> frgba_type ;

	typedef math::vector<value_type, 2> vector2 ;
	typedef math::vector<value_type, 3> vector3 ;
	typedef math::vector<value_type, 2> point2 ;
	typedef math::vector<value_type, 3> point3 ;
	typedef math::hierarchy_mesh_traits <value_type, 3>::FaceType face_type ;
	typedef math::hierarchy_mesh_traits <value_type, 3>::HalfEdgeType halfedge_type ;

	typedef math::HierarchyMesh<math::hierarchy_mesh_traits<value_type, 3> >  mesh_type ;
	typedef math::PseudoMesh<value_type , 3> pseudomesh_type ;
public :
	SolidBrush () {}
	SolidBrush (SolidBrush & brush): Brush(brush) {}

};

const float gaussian_constant[5] = 
//{0.22508352f, 0.11098164f, 0.01330373f, 0.00038771f, 0.0f};
{0.22508352f,   0.15098164f, 0.08330373f, 0.0338771f, 0.0f};
//{0.3308352f,  0.30098164f, 0.20330373f, 0.0638771f, 0.0f};
//{0.3308352f,  0.30098164f, 0.20330373f, 0.1638771f, 0.08f};

class GaussianBrush : public Brush
{
public :
	typedef float value_type ;
	typedef math::vector<float, 4> frgba_type ;

	typedef math::vector<value_type, 2> vector2 ;
	typedef math::vector<value_type, 3> vector3 ;
	typedef math::vector<value_type, 2> point2 ;
	typedef math::vector<value_type, 3> point3 ;
	typedef math::hierarchy_mesh_traits <value_type, 3>::FaceType face_type ;
	typedef math::hierarchy_mesh_traits <value_type, 3>::HalfEdgeType halfedge_type ;

	typedef math::HierarchyMesh<math::hierarchy_mesh_traits<value_type, 3> >  mesh_type ;
	typedef math::PseudoMesh<value_type , 3> pseudomesh_type ;
public:
	GaussianBrush ()  
	{
		this->m_is_blend = true; 
	}

	inline value_type linear_gaussian_blur(value_type distance_ratio) /*[0,1] from the center*/
	{
        int a = int(distance_ratio * 4);
        int b = a + 1;
        if (b >= 5) b = a;
        value_type weight_a = distance_ratio * 4 - a;
        value_type weight_b = 1.0f - weight_a;
        return ( gaussian_constant[a] * weight_b + gaussian_constant[b] * weight_a ) / gaussian_constant[0]  ;
	}	

	frgba_type evaluate_color (value_type ratio)
	{
		frgba_type color = get_color (); 
		color.a *= linear_gaussian_blur(ratio);
		return color ; 
	}
};

#endif
